package ezy.widget.view;

import android.content.Context;
import android.database.DataSetObserver;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;

import java.util.ArrayList;
import java.util.List;

public class BannerView extends ViewPager {

    private static final int TICK_INTERVAL = 1000;
    private static final int TICK_TIMES = 3;

    private boolean mSelecting = false;
    private boolean mTouching;
    private long mTicks = TICK_TIMES;

    private OnPageChangeListener mInnerListener = new OnPageChangeListener() {
        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
            if (mSelecting || mOuterListener == null) {
                return;
            }
            mOuterListener.onPageScrolled(mAdapter.toRealPosition(position), positionOffset, positionOffsetPixels);
        }

        @Override
        public void onPageSelected(final int position) {
            if (mSelecting) {
                return;
            }
            postDelayed(new Runnable() {
                @Override
                public void run() {
                    adjustCurrentItem(position);
                }
            }, 1);
            if (mOuterListener == null) {
                return;
            }
            mOuterListener.onPageSelected(mAdapter.toRealPosition(position));
        }
        @Override
        public void onPageScrollStateChanged(int state) {
            if (mSelecting || mOuterListener == null) {
                return;
            }
            mOuterListener.onPageScrollStateChanged(state);
        }
    };

    private OnPageChangeListener mOuterListener;

    @Override
    public void setOnPageChangeListener(OnPageChangeListener listener) {
        mOuterListener = listener;
    }

    private void adjustCurrentItem(int position) {
        mSelecting = true;
        if (position == 0) {
            setCurrentItemInternal(mAdapter.getCount() - 2, false);
        } else if (position == (mAdapter.getCount() - 1)) {
            setCurrentItemInternal(1, false);
        }
        mSelecting = false;
    }
    private void setCurrentItemInternal(int item, boolean smoothScroll) {
        super.setCurrentItem(item, smoothScroll);
        mTicks = TICK_TIMES;
    }
    private void tick() {
        if (mTicks > 0) {
            mTicks--;
            return;
        }
        mTicks = TICK_TIMES;
        if (!mTouching && mAdapter.canScroll()) {
            setCurrentItemInternal(super.getCurrentItem() + 1, true);
        }
    }

    public BannerView(Context context, AttributeSet attrs) {
        super(context, attrs);

        postDelayed(new Runnable() {
            @Override
            public void run() {
                tick();
                postDelayed(this, TICK_INTERVAL);
            }
        }, TICK_INTERVAL);
    }

    private InternalBannerAdapter mAdapter = new InternalBannerAdapter();

    @Override
    public void setAdapter(PagerAdapter value) {
        if (mAdapter.pa != null) {
            mAdapter.pa.unregisterDataSetObserver(mObserver);
            removeOnPageChangeListener(mInnerListener);
        }
        mAdapter.pa = value;
        if (value != null) {
            mAdapter.pa.registerDataSetObserver(mObserver);
            addOnPageChangeListener(mInnerListener);

            if (mAdapter.pa.getCount() != 0) {
                setCurrentItemInternal(1, false);
            }
            super.setAdapter(mAdapter);
        } else {
            super.setAdapter(null);
        }
    }
    @Override
    public PagerAdapter getAdapter() {
        return mAdapter.pa;
    }
    @Override
    public void setCurrentItem(int item, boolean smoothScroll) {
        setCurrentItemInternal(item + 1, smoothScroll);
    }
    @Override
    public void setCurrentItem(int item) {
        setCurrentItemInternal(item + 1, true);
    }
    @Override
    public int getCurrentItem() {
        return mAdapter.toRealPosition(super.getCurrentItem());
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        try {
            handleTouchEvent(event);
            return super.onTouchEvent(event);
        } catch (IllegalArgumentException ex) {
            // java.lang.IllegalArgumentException: pointerIndex out of range
            // ==> http://blog.csdn.net/nnmmbb/article/details/28419779
            ex.printStackTrace();
        }
        return false;
    }
    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        try {
            handleTouchEvent(event);
            return super.onInterceptTouchEvent(event);
        } catch (IllegalArgumentException ex) {
            ex.printStackTrace();
        }
        return false;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        int count = getChildCount();

        if (count > 0) {
            int width = MeasureSpec.getSize(widthMeasureSpec);
            //            int height = MeasureSpec.getSize(heightMeasureSpec);
            View child = getChildAt(0);
            child.measure(widthMeasureSpec, heightMeasureSpec);

            if (width == ViewGroup.LayoutParams.WRAP_CONTENT || width == 0) {
                widthMeasureSpec = MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(), MeasureSpec.EXACTLY);
            }
            //            if (height == ViewGroup.LayoutParams.WRAP_CONTENT || height == 0) {
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(child.getMeasuredHeight(), MeasureSpec.EXACTLY);
            //            }
        }

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    private boolean handleTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            mTouching = true;
            return true;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            mTouching = false;
            mTicks = TICK_TIMES;
            return true;
        }
        return false;
    }

    private DataSetObserver mObserver = new DataSetObserver() {
        @Override
        public void onChanged() {
            mAdapter.notifyDataSetChanged();
        }

        @Override
        public void onInvalidated() {
            mAdapter.notifyDataSetChanged();
        }
    };

    public interface BannerFactory<Item> {
        public View create(Item item, ViewGroup view,  int position);
    }

    public static class BannerAdapter<Item> extends PagerAdapter {
        private final BannerFactory<Item> mFactory;
        private List<Item> mItems = new ArrayList<>();

        public BannerAdapter(BannerFactory<Item> factory) {
            mFactory = factory;
        }

        public void update(List<Item> items) {
            mItems = items;
            notifyDataSetChanged();
        }

        @Override
        public int getCount() {
            return mItems.size();
        }


        @Override
        public Object instantiateItem(ViewGroup view, final int position) {
            return mFactory.create(mItems.get(position), view, position);
        }

        @Override
        public boolean isViewFromObject(View v, Object o) {
            return v == o;
        }

        @Override
        public void destroyItem(ViewGroup view, int position, Object object) {
            view.removeView((View)object);
        }
    }


    private static class InternalBannerAdapter extends PagerAdapter {

        PagerAdapter pa;

        //  0123  // 4
        // 301230 // 6
        // 012345 // 6
        int toRealPosition(int position) {
            if (pa == null) {
                return 0;
            }
            if (position == 0) {
                return pa.getCount() - 1;
            }
            if (position == getCount() - 1) {
                return 0;
            }
            return position - 1;
        }

        public boolean canScroll() {
            return pa != null && pa.getCount() > 1;
        }

        @Override
        public int getCount() {
            return pa.getCount() > 1 ? (pa.getCount() + 2) : pa.getCount();
        }

        @Override
        public boolean isViewFromObject(View v, Object o) {
            return pa.isViewFromObject(v, o);
        }

        @Override
        public void destroyItem(ViewGroup view, int position, Object object) {
            pa.destroyItem(view, position, object);
        }

        @Override
        public Object instantiateItem(ViewGroup view, final int position) {
            return  pa.instantiateItem(view, toRealPosition(position));
        }
    }
}
